﻿/************************************************************************

   Extended Silverlight Toolkit

   Copyright (C) 2010-2012 Xceed Software Inc.

   This program is provided to you under the terms of the Microsoft Public
   License (Ms-PL) as published at http://extendedsilverlight.codeplex.com/license

   Please purchase a commercial version of this toolkit if you are using
   it in a commercial product. The commercial versions support the project,
   and also include more features for each control, priority support,
   source code, updates, removed watermark, and a proprietary license.

   Visit http://xceed.com and follow @datagrid on Twitter.

  **********************************************************************/

using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Diagnostics;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;

namespace Xceed.Silverlight.DataGrid
{
  #region Metadata

  [TemplateVisualState( GroupName = "EditingStates", Name = "NotBeingEdited" )]
  [TemplateVisualState( GroupName = "EditingStates", Name = "BeingEdited" )]
  [TemplateVisualState( GroupName = "EditingStates", Name = "Committing" )]

  [TemplateVisualState( GroupName = "ValidationStates", Name = "ValidData" )]
  [TemplateVisualState( GroupName = "ValidationStates", Name = "InvalidData" )]

  #endregion

  public abstract class EditableRow : Row
  {
    #region Static Fields

    internal static readonly XceedRoutedEvent EditableRowMouseLeftButtonDownEvent = new XceedRoutedEvent();

    private static readonly string EditingStateUnchanged = "NotBeingEdited";
    private static readonly string EditingStateEdited = "BeingEdited";
    private static readonly string EditingStateCommitting = "Committing";

    private static readonly string ValidationStateValid = "ValidData";
    private static readonly string ValidationStateInvalid = "InvalidData";

    #endregion

    #region Constructor

    internal EditableRow()
    {
      this.DefaultStyleKey = typeof( EditableRow );

      m_emptyValidationErrors = new Lazy<ReadOnlyObservableCollection<RowValidationError>>(
                                  () => new ReadOnlyObservableCollection<RowValidationError>(
                                          new ObservableCollection<RowValidationError>() ) );

      m_keyHandler = new RowEditKeyHandler( this, KeyHandler.Default );
    }

    #endregion

    #region ValidationErrors Property

    public ReadOnlyObservableCollection<RowValidationError> ValidationErrors
    {
      get
      {
        if( m_viewModel != null )
          return m_viewModel.ValidationErrors;

        return m_emptyValidationErrors.Value;
      }
    }

    private readonly Lazy<ReadOnlyObservableCollection<RowValidationError>> m_emptyValidationErrors;

    #endregion

    #region HasValidationErrors Property

    public bool HasValidationErrors
    {
      get
      {
        if( m_viewModel != null )
          return m_viewModel.HasValidationErrors;

        return false;
      }
    }

    #endregion

    #region HasRestrictiveValidationErrors Property

    public bool HasRestrictiveValidationErrors
    {
      get
      {
        if( m_viewModel != null )
          return m_viewModel.HasRestrictiveValidationErrors;

        return false;
      }
    }

    #endregion

    #region ValidationErrorToolTip Property

    public ToolTip ValidationErrorToolTip
    {
      get
      {
        return m_validationErrorToolTip;
      }
      private set
      {
        if( value != m_validationErrorToolTip )
        {
          m_validationErrorToolTip = value;

          this.RaisePropertyChanged( () => this.ValidationErrorToolTip );
        }
      }
    }

    private void UpdateValidationErrorToolTip()
    {
      if( !this.HasValidationErrors )
      {
        this.ValidationErrorToolTip = null;
      }
      else
      {
        ToolTip validationErrorToolTip = this.ValidationErrorToolTip;
        if( validationErrorToolTip == null )
        {
          validationErrorToolTip = new ToolTip();
          validationErrorToolTip.ContentTemplate = this.ValidationErrorToolTipContentTemplate;

          this.ValidationErrorToolTip = validationErrorToolTip;
        }

        validationErrorToolTip.Content = this.ValidationErrors;
      }
    }

    private ToolTip m_validationErrorToolTip;

    #endregion

    #region ValidationErrorToolTipContentTemplate Property

    public static readonly DependencyProperty ValidationErrorToolTipContentTemplateProperty = DependencyProperty.Register(
      "ValidationErrorToolTipContentTemplate",
      typeof( DataTemplate ),
      typeof( EditableRow ),
      new PropertyMetadata( new PropertyChangedCallback( EditableRow.OnValidationErrorToolTipContentTemplateChanged ) ) );

    public DataTemplate ValidationErrorToolTipContentTemplate
    {
      get
      {
        return ( DataTemplate )this.GetValue( EditableRow.ValidationErrorToolTipContentTemplateProperty );
      }
      set
      {
        this.SetValue( EditableRow.ValidationErrorToolTipContentTemplateProperty, value );
      }
    }

    private static void OnValidationErrorToolTipContentTemplateChanged( DependencyObject sender, DependencyPropertyChangedEventArgs e )
    {
      var row = sender as EditableRow;
      if( row == null )
        return;

      if( row.ValidationErrorToolTip != null )
      {
        row.ValidationErrorToolTip.ContentTemplate = e.NewValue as DataTemplate;
      }
    }

    #endregion

    #region ValidationState Property

    public ValidationState ValidationState
    {
      get
      {
        return m_validationState;
      }
      private set
      {
        if( value != m_validationState )
        {
          m_validationState = value;

          this.RaisePropertyChanged( () => this.ValidationState );
        }
      }
    }

    private ValidationState m_validationState = ValidationState.Valid;

    #endregion

    #region ViewModel Internal Property

    internal EditableRowViewModel ViewModel
    {
      get
      {
        return m_viewModel;
      }
      set
      {
        if( value == m_viewModel )
          return;

        if( m_viewModel != null )
        {
          m_viewModel.PropertyChanged -= new PropertyChangedEventHandler( this.OnViewModelPropertyChanged );
          m_viewModel.Releasing -= new EventHandler( this.OnViewModelReleasing );
        }

        m_viewModel = value;

        if( m_viewModel != null )
        {
          m_viewModel.PropertyChanged += new PropertyChangedEventHandler( this.OnViewModelPropertyChanged );
          m_viewModel.Releasing += new EventHandler( this.OnViewModelReleasing );
        }

        this.OnViewModelChanged();

        this.RaisePropertyChanged( () => this.ViewModel );
      }
    }

    internal virtual void OnViewModelChanged()
    {
      this.UpdateDataContext();
      this.UpdateEditingVisualState();
      this.UpdateValidationVisualState();
    }

    internal virtual void OnViewModelPropertyChanged( PropertyChangedEventArgs e )
    {
      if( e.IsPropertyChanged( ( EditableRowViewModel item ) => item.EditingState ) )
      {
        this.UpdateEditingVisualState();
      }

      if( ( e.IsPropertyChanged( ( EditableRowViewModel item ) => item.HasValidationErrors ) )
        || ( e.IsPropertyChanged( ( EditableRowViewModel item ) => item.HasRestrictiveValidationErrors ) )
        || ( e.IsPropertyChanged( ( EditableRowViewModel item ) => item.HasCellsValidationErrors ) )
        || ( e.IsPropertyChanged( ( EditableRowViewModel item ) => item.HasCellsRestrictiveValidationErrors ) ) )
      {
        this.UpdateValidationErrors();
      }
    }

    private void OnViewModelPropertyChanged( object sender, PropertyChangedEventArgs e )
    {
      this.OnViewModelPropertyChanged( e );
    }

    private void OnViewModelReleasing( object sender, EventArgs e )
    {
      EditableRowViewModel viewModel = ( EditableRowViewModel )sender;

      Debug.Assert( viewModel == this.ViewModel );

      this.ViewModel = null;
    }

    private EditableRowViewModel m_viewModel;

    #endregion

    #region Public Editing Methods

    public bool BeginEdit()
    {
      var e = new XceedRoutedEventRequestEditEventArgs( this );
      this.EventManager.RaiseEvent( EditableRoutedEvents.RequestBeginEditEvent, e );

      return e.Result;
    }

    public bool EndEdit()
    {
      var e = new XceedRoutedEventRequestEditEventArgs( this );
      this.EventManager.RaiseEvent( EditableRoutedEvents.RequestEndEditEvent, e );

      return e.Result;
    }

    public bool CancelEdit()
    {
      var e = new XceedRoutedEventRequestEditEventArgs( this );
      this.EventManager.RaiseEvent( EditableRoutedEvents.RequestCancelEditEvent, e );

      return e.Result;
    }

    #endregion

    #region VisualState Methods

    private void SetEditingVisualState( string state, bool useTransitions )
    {
      this.VisualStateHelper.GoToEditingState( state, useTransitions );
    }

    private void SetValidationState( string state, bool useTransitions )
    {
      this.VisualStateHelper.GoToValidationState( state, useTransitions );
    }

    private void UpdateEditingVisualState()
    {
      RowEditingState state = ( m_viewModel != null )
                                ? m_viewModel.EditingState
                                : RowEditingState.Unchanged;

      switch( state )
      {
        case RowEditingState.Edited:
        case RowEditingState.Canceling:
        case RowEditingState.Ending:
          this.SetEditingVisualState( EditableRow.EditingStateEdited, true );
          break;

        case RowEditingState.Committing:
          this.SetEditingVisualState( EditableRow.EditingStateCommitting, true );
          break;

        default:
          this.SetEditingVisualState( EditableRow.EditingStateUnchanged, true );
          break;
      }
    }

    private void UpdateValidationVisualState()
    {
      bool hasErrors = false;

      EditableRowViewModel viewModel = this.ViewModel;
      if( viewModel != null )
      {
        hasErrors = ( viewModel.HasValidationErrors )
                 || ( viewModel.HasCellsValidationErrors );
      }

      if( hasErrors )
      {
        this.ValidationState = Xceed.Silverlight.DataGrid.ValidationState.Invalid;
        this.SetValidationState( EditableRow.ValidationStateInvalid, true );
      }
      else
      {
        this.ValidationState = Xceed.Silverlight.DataGrid.ValidationState.Valid;
        this.SetValidationState( EditableRow.ValidationStateValid, true );
      }
    }

    #endregion

    protected override void OnKeyDown( KeyEventArgs e )
    {
      base.OnKeyDown( e );

      m_keyHandler.HandleRequest( e );
    }

    protected override void OnMouseLeftButtonDown( MouseButtonEventArgs e )
    {
      XceedEventUtils.RaiseMouseButtonEvent( this, e, EditableRow.EditableRowMouseLeftButtonDownEvent );
    }

    private void UpdateDataContext()
    {
      object dataItem = null;
      if( m_viewModel != null )
      {
        dataItem = m_viewModel.DataItem;
      }

      if( this.DataContext != dataItem )
      {
        this.DataContext = dataItem;
      }
    }

    private void UpdateValidationErrors()
    {
      this.RaisePropertyChanged( () => this.ValidationErrors );
      this.RaisePropertyChanged( () => this.HasValidationErrors );
      this.RaisePropertyChanged( () => this.HasRestrictiveValidationErrors );

      this.UpdateValidationVisualState();
      this.UpdateValidationErrorToolTip();
    }

    #region Private Fields

    private readonly KeyHandler m_keyHandler;

    #endregion

    #region RowEditKeyHandler Nested Type

    private sealed class RowEditKeyHandler : EditKeyHandler
    {
      internal RowEditKeyHandler( EditableRow targetRow, KeyHandler successor )
        : base( successor )
      {
        if( targetRow == null )
          throw new ArgumentNullException( "targetRow" );

        m_targetRow = targetRow;
      }

      protected override bool BeginEdit()
      {
        return m_targetRow.BeginEdit();
      }

      protected override bool EndEdit()
      {
        return m_targetRow.EndEdit();
      }

      protected override bool CancelEdit()
      {
        return m_targetRow.CancelEdit();
      }

      private readonly EditableRow m_targetRow;
    }

    #endregion
  }
}
